package cc.owoo.godpen.network.reptile.template;

import cc.owoo.godpen.thread.SyncDownloadThread;
import cc.owoo.godpen.network.reptile.LocationScan;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * 列表页面爬取器
 * Created by nimensei
 * 2022-05-02 上午 11:36
 **/
public abstract class ListPageReptile<T> {
    public SyncDownloadThread downloadThread;// 下载线程
    private int total;// 总页数
    private boolean isStop = true;// 是否已停止
    private Order order = Order.flashback;// 迭代顺序
    private OnIndexChange onPage;// 页面完成进度监听器
    private OnIndexChange onLocation;// 找到页面位置是调用
    private Runnable onStop;// 停止
    private Runnable onExceptionStop;// 异常停止

    /**
     * 获取总页数
     *
     * @return 总页数
     */
    public abstract int total();

    /**
     * 监测该页面是否已经爬取过
     *
     * @param index 页面下标（从0开始）
     * @param real  按照排序方式的实际下标
     * @return 是否爬取过
     */
    public abstract boolean check(int index, int real);

    /**
     * 判断数据是否已经存在
     *
     * @param data 数据
     * @return 是否存在
     */
    public abstract boolean contains(T data);

    /**
     * 请求数据
     *
     * @param index 页面下标（从0开始）
     * @param real  按照排序方式的实际下标
     * @return 当前页面的数据
     */
    public abstract List<T> request(int index, int real);

    /**
     * 获取成功（线程安全）
     *
     * @param data 数据
     */
    public abstract void success(T data);

    /**
     * 运行
     *
     * @param index 页面位置，如果传入小于0的值，则自动使用二分位置
     */
    public void start(int index) {
        synchronized (this) {
            if (!isStop)
                return;
            isStop = false;
        }
        total = total();
        if (total == -1)
            throw new IllegalArgumentException("页面总数获取失败");
        cc.owoo.godpen.network.reptile.LocationScan locationScan = new LocationScan() {
            @Override
            public boolean check(int index) {
                return ListPageReptile.this.check(index, index(index));
            }
        };
        index = locationScan.scan(index, total);
        if (onLocation != null)
            onLocation.on(index, total);
        SyncDownloadThread task = downloadThread;
        if (task == null)
            task = new SyncDownloadThread();
        LinkedList<Success<T>> successList = new LinkedList<>();
        int successIndex = index;
        Runnable runnable = new Runnable() {
            int index = successIndex;

            @Override
            public void run() {
                successList.sort((a, b) -> a.i - b.i);
                while (successList.size() > 0) {
                    Success<T> success = successList.getFirst();
                    if (success.i != index)
                        break;
                    for (T item : success.data)
                        try {
                            if (!contains(item))
                                success(item);
                        } catch (Exception e) {
                            e.printStackTrace();
                            if (!isStop && onExceptionStop != null)
                                onExceptionStop.run();
                            stop();
                        }
                    successList.removeFirst();
                    if (onPage != null)
                        onPage.on(index, total);
                    ++index;
                }
            }
        };
        while (index < total) {
            int i = index;
            task.execute(() -> {
                if (isStop)
                    return;
                List<T> list;
                try {
                    list = request(i, index(i));
                } catch (Exception e) {
                    e.printStackTrace();
                    if (!isStop && onExceptionStop != null)
                        onExceptionStop.run();
                    stop();
                    return;
                }
                if (list == null) {
                    if (!isStop && onExceptionStop != null)
                        onExceptionStop.run();
                    stop();
                    return;
                }
                if (order == Order.flashback)
                    Collections.reverse(list);
                synchronized (successList) {
                    successList.add(new Success<>(i, list));
                    runnable.run();
                }
            });
            if (isStop)
                break;
            ++index;
        }
        task.finish();
    }

    /**
     * 停止
     */
    public void stop() {
        synchronized (this) {
            if (isStop)
                return;
            isStop = true;
        }
        if (onStop != null)
            onStop.run();
    }

    /**
     * 获取实际下标
     *
     * @param i 顺序下标
     * @return 实际下标
     */
    private int index(int i) {
        return order == Order.sequence ? i : total - i - 1;
    }

    /**
     * 设置下载线程
     *
     * @param downloadThread 下载线程
     */
    public void setDownloadThread(SyncDownloadThread downloadThread) {
        this.downloadThread = downloadThread;
    }

    /**
     * 获取下载线程
     *
     * @return 下载线程
     */
    public SyncDownloadThread getDownloadThread() {
        return downloadThread;
    }

    /**
     * 设置迭代顺序
     *
     * @param order 迭代顺序
     */
    public void setOrder(Order order) {
        this.order = order;
    }

    /**
     * 获取迭代顺序
     *
     * @return 迭代顺序
     */
    public Order getOrder() {
        return order;
    }

    /**
     * 设置页面完成进度监听器（线程安全）
     *
     * @param onPage 页面完成进度监听器
     */
    public void setOnPage(OnIndexChange onPage) {
        this.onPage = onPage;
    }

    /**
     * 获取页面完成进度监听器
     *
     * @return 页面完成进度监听器
     */
    public OnIndexChange getOnPage() {
        return onPage;
    }

    /**
     * 设置位置扫描监听器
     *
     * @param onLocation 位置扫描监听器
     */
    public void setOnLocation(OnIndexChange onLocation) {
        this.onLocation = onLocation;
    }

    /**
     * 获取位置扫描监听器
     *
     * @return 位置扫描监听器
     */
    public OnIndexChange getOnLocation() {
        return onLocation;
    }

    /**
     * 设置停止事件监听器
     *
     * @param onStop 停止事件监听器
     */
    public void setOnStop(Runnable onStop) {
        this.onStop = onStop;
    }

    /**
     * 获取停止事件监听器
     *
     * @return 停止事件监听器
     */
    public Runnable getOnStop() {
        return onStop;
    }

    /**
     * 设置异常停止回调函数
     *
     * @param onExceptionStop 设置异常停止回调函数
     */
    public void setOnExceptionStop(Runnable onExceptionStop) {
        this.onExceptionStop = onExceptionStop;
    }

    /**
     * 获取异常停止回调函数
     *
     * @return 异常停止回调函数
     */
    public Runnable getOnExceptionStop() {
        return onExceptionStop;
    }

    /**
     * 迭代顺序
     */
    public enum Order {
        sequence, flashback
    }

    /**
     * 获取成功的数据
     *
     * @param <T> 数据类型
     */
    private record Success<T>(int i, List<T> data) {
    }

    /**
     * 下标变动回调函数
     */
    public interface OnIndexChange {
        void on(int index, int total);
    }
}
